//
//  TruncationDetector.m
//  TruncationDetector
//
//  Created by taodongl on 8/16/15.
//  Copyright (c) 2015 taodongl. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import "AsyncSocket/AsyncSocket.h"
#import "WindowSniffer.h"
#import "TinyHttpd.h"
@interface DetectorServer : NSObject <AsyncSocketDelegate>
{
    AsyncSocket *serverSocket;
    NSNetService *netService;
    NSMutableArray *connectedSockets;
}
@property (nonatomic, assign, readwrite ) CFRunLoopRef   loopRef;
@property (nonatomic, assign, readwrite ) BOOL           isRunning;
@end

@interface SocketCache : NSObject

@property (nonatomic, strong, readwrite)    AsyncSocket *socket;
@property (nonatomic, strong, readwrite)    NSData *buffer;

@end

static  DetectorServer *pDetectorInstance = nil;
static  int Initialized = 0;
__attribute__((constructor))
static void initializor()
{
    NSLog(@"initializing detector framework");
    if (Initialized != 0) {
        return;
    }
    Initialized ++;
    // create the singleton object
    pDetectorInstance = [[DetectorServer alloc] init];
}

__attribute__((destructor))
static void finalizer()
{
    if (pDetectorInstance != nil && pDetectorInstance.isRunning == true) {
        pDetectorInstance.isRunning = false;
        CFRunLoopStop(pDetectorInstance.loopRef);
    }
    Initialized = 0;
}

@implementation SocketCache
@synthesize socket = _socket;
@synthesize buffer = _buffer;
- (id) initWithSocket: (AsyncSocket *) socket
{
    self.socket = socket;
    self.buffer = nil;
    return self;
}
@end

@implementation DetectorServer
- (id) init
{
    self = [super init];
    if (self)
    {
        self.isRunning = false;
        connectedSockets = [[NSMutableArray alloc] initWithCapacity:1];
        // start specific thread
        [NSThread detachNewThreadSelector:@selector(doLoop:) toTarget:self withObject:(id)nil];
    }
    return self;
}

+ (NSData*) convertToHtml: (NSData*) img
{
    NSString* base64 = [img base64EncodedStringWithOptions: (NSDataBase64EncodingOptions)0];
    NSString* templateHtml = @"<!DOCTYPE html>\r\n"
    "<html lang='en'>\r\n"
    "<head>"
    "   <meta charset='utf-8'>"
    "</head>"
    "<body>"
    "<img src=\"data:img/png;base64,%@\"/>"
    "</body>"
    "</html>";
    NSString* response = [NSString stringWithFormat: templateHtml, base64];
    NSData* data = [response dataUsingEncoding: NSUTF8StringEncoding];
    return data;
}

- (void) doLoop:(id)p
{
    BOOL success;
    UInt16 port = 0;
    
    success = [self setupServerSocket];
    if (!success) {
        return;
    }
    port = [serverSocket localPort];
    // publish service
    success = [self publishService:port];
    if (!success) {
        return;
    }
    NSLog(@"start listening the command");
    self.isRunning = true;
    self.loopRef = CFRunLoopGetCurrent();
    CFRunLoopRun();
}

- (BOOL) setupServerSocket
{
    BOOL success;
    NSError *error = nil;
    UInt16 port = 0;
    serverSocket = [[AsyncSocket alloc] initWithDelegate:self];
    [serverSocket setRunLoopModes:[NSArray arrayWithObject:NSDefaultRunLoopMode]];
    success = [serverSocket acceptOnPort:port error:&error];
    if (!success) {
        NSLog(@"fail to create detector server socket");
        return false;
    }
    return success;
}

- (BOOL) publishService:(UInt16)port
{
    BOOL success = false;
    netService = [[NSNetService alloc] initWithDomain:@"local" type:@"_x-UiTruncation._tcp" name:@"Detector" port:port];
    success = (netService != nil);
    if (success) {
        NSLog(@"publish Detector._x-UiTruncation._tcp.local at port=%d", port);
        [netService publishWithOptions:NSNetServiceNoAutoRename];
    }
    return success;
}

- (void) requestHtmlReport:(SocketCache *)cache
{
    WindowSniffer *sniffer = [[WindowSniffer alloc] init];
    NSString *response = [sniffer reportHtml];
    if (response != nil && response.length > 0) {
        cache.buffer = [response dataUsingEncoding: NSUTF8StringEncoding];
    }
}

- (void) requestJsonReport:(SocketCache *)cache
{
    WindowSniffer *sniffer = [[WindowSniffer alloc] init];
    NSString *response = [sniffer reportJson];
    if (response != nil && response.length > 0) {
        cache.buffer = [response dataUsingEncoding: NSUTF8StringEncoding];
    }
}


/* implement interface of AsyncSocketDelegate */


- (void) onSocket:(AsyncSocket *)socket didAcceptNewSocket:(AsyncSocket *)newSocket
{
    NSLog(@"new client is coming");
    NSUInteger index = 0;
    SocketCache* cache = nil;
    for (SocketCache* obj in connectedSockets) {
        if (obj.socket == nil) {
            break;
        }
        index ++;
    }
    if (index >= connectedSockets.count) {
        cache = [[SocketCache alloc] initWithSocket: newSocket];
        [connectedSockets addObject:cache];
    } else {
        cache = [connectedSockets objectAtIndex: index];
        cache.socket = newSocket;
    }
}

- (void) onSocketDidDisconnect:(AsyncSocket *)oldSocket
{
    NSLog(@"==> Release client");
    SocketCache* cache = nil;
    NSUInteger index = 0;
    for (SocketCache* obj in connectedSockets) {
        if (obj.socket == oldSocket) {
            cache = obj;
            index ++;
            continue;
        }
        if (obj.socket == nil) {
            index ++;
        }
    }
    if (cache != nil) {
        cache.socket = nil;
        cache.buffer = nil;
    }
    if (index > 0 && index == [connectedSockets count]) {
        [connectedSockets removeAllObjects];
    }
}

- (void) onSocket:(AsyncSocket *)socket didConnectToHost:(NSString *)host port:(UInt16)port
{
    NSLog(@"==> Accepted client %@:%hu", host, port);
    NSUInteger index = 0;
    for (SocketCache* obj in connectedSockets) {
        if (obj.socket == socket) {
            break;
        }
        index ++;
    }
    assert(index < connectedSockets.count);
    [socket readDataWithTimeout:-1 tag: index];
}

- (void) onSocket:(AsyncSocket *)socket didReadData:(NSData *)data withTag:(long)index
{
    NSLog(@"command is coming");
    if (data == nil) {
        NSLog(@"command is empty");
        return;
    }
    TinyHttpd* httpd = [[TinyHttpd alloc] initWithData: data];
    if (![httpd isValid]) {
        NSData * errMsg = [httpd error];
        [socket writeData: errMsg withTimeout:-1 tag:1];
    } else {
        assert(index < [connectedSockets count]);
        SocketCache * cache = [connectedSockets objectAtIndex: index];
        assert(cache != nil);
        if ([httpd isRequestHtml])
            [self performSelectorOnMainThread:@selector(requestHtmlReport:) withObject:cache waitUntilDone:true];
        else
            [self performSelectorOnMainThread:@selector(requestJsonReport:) withObject:cache waitUntilDone:true];
        NSData *successMsg;
        if (cache.buffer != nil) {
            successMsg = [httpd success: cache.buffer];
            cache.buffer = nil;
        } else {
            successMsg = [httpd success];
        }
        [socket writeData: successMsg withTimeout:-1 tag: index];
    }
    
    // prepare to read again
    [socket readDataWithTimeout:-1 tag: index];
}

@end

